import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart' show timeDilation;

class Photo {
  const Photo(this.asset, this.id);

  final String asset;
  final int id;

  @override
  bool operator ==(dynamic other) {
    if (runtimeType != other.runtimeType) return false;
    final Photo typedOther = other;
    return id == typedOther.id;
  }

  @override
  int get hashCode => id.hashCode;
}

final List<Photo> allPhotos = List<Photo>.generate(11, (int index) {
  return Photo('assets/pic${index + 1}.jpg', index);
});

class PhotoFrame {
  const PhotoFrame(this.width, this.height);

  final double width;
  final double height;
}

// Defines the app's repeating photo layout "block" in terms of
// photoBlockFrames.length rows of photos.
//
// Each photoBlockFrames[i] item defines the layout of one row of photos
// in terms  of their relative widgets and heights. In a row: widths must
// sum to 1.0, heights must be the same.
final List<List<PhotoFrame>> photoBlockFrames = [
  [const PhotoFrame(1.0, 0.4)],
  [const PhotoFrame(0.25, 0.3), const PhotoFrame(0.75, 0.3)],
  [const PhotoFrame(0.75, 0.3), const PhotoFrame(0.25, 0.3)],
];

class PhotoCheck extends StatelessWidget {
  const PhotoCheck({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return DecoratedBox(
      decoration: BoxDecoration(
        color: Theme.of(context).primaryColor,
        borderRadius: BorderRadius.all(const Radius.circular(16.0)),
      ),
      child: const Icon(
        Icons.check,
        size: 32.0,
        color: Colors.white,
      ),
    );
  }
}

class PhotoItem extends StatefulWidget {
  PhotoItem({
    Key key,
    this.photo,
    this.color,
    this.onTap,
    this.selected,
  }) : super(key: key);

  final Photo photo;
  final Color color;
  final VoidCallback onTap;
  final bool selected;

  @override
  _PhotoItemState createState() => _PhotoItemState();
}

class _PhotoItemState extends State<PhotoItem> with TickerProviderStateMixin {
  AnimationController _selectController;
  Animation<double> _stackScaleAnimation;
  Animation<RelativeRect> _imagePositionAnimation;
  Animation<double> _checkScaleAnimation;
  Animation<double> _checkSelectedOpacityAnimation;

  AnimationController _replaceController;
  Animation<Offset> _replaceNewPhotoAnimation;
  Animation<Offset> _replaceOldPhotoAnimation;
  Animation<double> _removeCheckAnimation;

  Photo _oldPhoto;
  Photo _newPhoto; // non-null during a remove animation

  @override
  void initState() {
    super.initState();

    _oldPhoto = widget.photo;

    _selectController = AnimationController(
        duration: const Duration(milliseconds: 300), vsync: this);
    final Animation<double> easeSelection = CurvedAnimation(
      parent: _selectController,
      curve: Curves.easeIn,
    );
    _stackScaleAnimation =
        Tween<double>(begin: 1.0, end: 0.85).animate(easeSelection);
    _checkScaleAnimation =
        Tween<double>(begin: 0.0, end: 1.25).animate(easeSelection);
    _checkSelectedOpacityAnimation =
        Tween<double>(begin: 0.0, end: 1.0).animate(easeSelection);
    _imagePositionAnimation = RelativeRectTween(
      begin: const RelativeRect.fromLTRB(0.0, 0.0, 0.0, 0.0),
      end: const RelativeRect.fromLTRB(32.0, 32.0, 32.0, 32.0),
    ).animate(easeSelection);

    _replaceController = AnimationController(
        duration: const Duration(milliseconds: 300), vsync: this);

    final Animation<double> easeInsert = CurvedAnimation(
      parent: _replaceController,
      curve: Curves.easeIn,
    );

    _replaceNewPhotoAnimation = Tween<Offset>(
      begin: const Offset(1.0, 0.0),
      end: Offset.zero,
    ).animate(easeInsert);

    _replaceOldPhotoAnimation = Tween<Offset>(
      begin: const Offset(0.0, 0.0),
      end: const Offset(-1.0, 0.0),
    ).animate(easeInsert);

    _removeCheckAnimation = Tween<double>(begin: 1.0, end: 0.0).animate(
      CurvedAnimation(
        parent: _replaceController,
        curve: Interval(0.0, 0.25, curve: Curves.easeIn),
      ),
    );
  }

  @override
  void dispose() {
    _selectController.dispose();
    _replaceController.dispose();
    super.dispose();
  }

  @override
  void didUpdateWidget(PhotoItem oldWidget) {
    super.didUpdateWidget(oldWidget);

    if (widget.photo != oldWidget.photo) {
      print("didUpdateWidget->${widget.photo.id}  ${oldWidget.photo.id}");
      _replace(oldWidget.photo, widget.photo);
    }

    if (widget.selected != oldWidget.selected) _select();
  }

  Future<void> _replace(Photo oldPhoto, Photo newPhoto) async {
    try {
      setState(() {
        _oldPhoto = oldPhoto;
        _newPhoto = newPhoto;
      });
      await _replaceController.forward().orCancel;
      setState(() {
        _oldPhoto = newPhoto;
        _newPhoto = null;
        _replaceController.value = 0.0;
        _selectController.value = 0.0;
      });
    } on TickerCanceled {
      print('canceled!'); //never reached...
    }
  }

  void _select() {
    if (widget.selected)
      _selectController.forward();
    else
      _selectController.reverse();
  }

  Widget build(BuildContext context) {
    return Stack(
      children: <Widget>[
        /// 填满Stack的所有的可用空间
        Positioned.fill(
          /// 配合[SlideTransition]实现裁剪功能
          child: ClipRect(
            /// [SlideTransition]底层是基于[FractionalTranslation]的，[FractionalTranslation]类中的`final Offset translation`的注释：
            /// The translation to apply to the child, scaled to the child's size.
            /// For example, an [Offset] with a `dx` of 0.25 will result in a horizontal
            /// translation of one quarter the width of the child.、
            /// 所以[SlideTransition]平移动画中的[Offset]对象的取值范围必然是[0.0,1.0]之间的
            child: SlideTransition(
              position: _replaceOldPhotoAnimation,
              child: Material(
                color: widget.color,
                child: InkWell(
                  onTap: widget.onTap,

                  /// 配合[PositionedTransition]使用
                  child: ScaleTransition(
                    scale: _stackScaleAnimation,
                    child: Stack(
                      children: <Widget>[
                        /// TODO PositionedTransition 与 ScaleTransition 的区别：前者仅仅是改变控件在Stack中的位置，
                        /// 而不改变尺寸，如果不同步更改Scale的话，则只显示在 PositionedTransition 定义的Rect的部分，其余部分会被裁剪掉
                        /// 主体图片的缩放->
                        PositionedTransition(
                          rect: _imagePositionAnimation,
                          child: Image.asset(
                            _oldPhoto.asset,
                            fit: BoxFit.cover,
                          ),
                        ),

                        /// 选中按钮
                        Positioned(
                          top: 0.0,
                          left: 0.0,
                          child: FadeTransition(
                            opacity: _checkSelectedOpacityAnimation,
                            child: FadeTransition(
                              opacity: _removeCheckAnimation,
                              child: ScaleTransition(
                                alignment: Alignment.topLeft,
                                scale: _checkScaleAnimation,
                                child: const PhotoCheck(),
                              ),
                            ),
                          ),
                        ),

                        /// 这也是一个涵盖整个[Stack]大小的矩形动画，rect中的l/t/r/b并不是界定这个rect的大小，而是距离边界的值
                        /// Text是相对于这个[PositionedTransition]的右上角的位置
                        /// index文字
                        PositionedTransition(
                          rect: _imagePositionAnimation,
                          child: Container(
                            margin: const EdgeInsets.all(8.0),
                            alignment: Alignment.topRight,
                            child: Text(
                              widget.photo.id.toString(),
                              style: TextStyle(color: Colors.green),
                            ),
                          ),
                        ),
                      ],
                    ),
                  ),
                ),
              ),
            ),
          ),
        ),
        Positioned.fill(
          child: ClipRect(
            child: SlideTransition(
              position: _replaceNewPhotoAnimation,
              child: _newPhoto == null
                  ? null
                  : Image.asset(
                      _newPhoto.asset,
                      fit: BoxFit.cover,
                    ),
            ),
          ),
        ),
      ],
    );
  }
}

class ImagesDemo extends StatefulWidget {
  @override
  _ImagesDemoState createState() => _ImagesDemoState();
}

class _ImagesDemoState extends State<ImagesDemo>
    with SingleTickerProviderStateMixin {
  static const double _photoBlockHeight = 576.0;

  int _selectedPhotoIndex;

  void _selectPhoto(int photoIndex) {
    setState(() {
      _selectedPhotoIndex =
          photoIndex == _selectedPhotoIndex ? null : photoIndex;
    });
  }

  void _removeSelectedPhoto() {
    if (_selectedPhotoIndex == null) return;
    setState(() {
      allPhotos.removeAt(_selectedPhotoIndex);
      _selectedPhotoIndex = null;
    });
  }

  Widget _buildPhotoBlock(
      BuildContext context, int blockIndex, int blockFrameCount) {
    final List<Widget> rows = [];

    int startPhotoIndex = blockIndex * blockFrameCount;
    final Color photoColor = Colors.grey[500];
    for (int rowIndex = 0; rowIndex < photoBlockFrames.length; rowIndex += 1) {
      final List<Widget> rowChildren = [];
      final int rowLength = photoBlockFrames[rowIndex].length;
      for (int frameIndex = 0; frameIndex < rowLength; frameIndex += 1) {
        final PhotoFrame frame = photoBlockFrames[rowIndex][frameIndex];
        final int photoIndex = startPhotoIndex + frameIndex;
        rowChildren.add(
          Expanded(
            flex: (frame.width * 100.0).toInt(),
            child: Container(
              padding: const EdgeInsets.all(4.0),
              height: frame.height * _photoBlockHeight,
              child: PhotoItem(
                photo: allPhotos[photoIndex],
                color: photoColor,
                selected: photoIndex == _selectedPhotoIndex,
                onTap: () {
                  _selectPhoto(photoIndex);
                },
              ),
            ),
          ),
        );
      }
      rows.add(Row(
        crossAxisAlignment: CrossAxisAlignment.center,
        children: rowChildren,
      ));
      startPhotoIndex += rowLength;
    }

    return Column(children: rows);
  }

  @override
  Widget build(BuildContext context) {
    Future.delayed(const Duration(seconds: 1)).then((v) {
      timeDilation = 20.0; // 1.0 is normal animation speed.
    });
    // Number of PhotoBlockFrames in each _photoBlockHeight block
    final int photoBlockFrameCount =
        photoBlockFrames.map((List<PhotoFrame> l) => l.length).reduce((s, n) {
      print("--------key:$s value:$n");
      return s + n;
    });
    print("result--------->$photoBlockFrameCount");
    return Scaffold(
      appBar: AppBar(
        title: const Text('Images Demo'),
        actions: <Widget>[
          IconButton(
            icon: Icon(Icons.delete),
            onPressed: _removeSelectedPhoto,
          ),
        ],
      ),
      body: SizedBox.expand(
        child: ListView.builder(
          padding: EdgeInsets.all(4.0),
          itemExtent: _photoBlockHeight,
          itemCount: (allPhotos.length / photoBlockFrameCount).floor(),
          itemBuilder: (BuildContext context, int blockIndex) {
            return _buildPhotoBlock(context, blockIndex, photoBlockFrameCount);
          },
        ),
      ),
    );
  }
}

//void main() {
//  runApp(MaterialApp(
//      theme: ThemeData.light().copyWith(
//          backgroundColor: Colors.green, primaryColor: Colors.blueGrey),
//      home: ImagesDemo()));
//}

class ClipRectView extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return _ClipRectState();
  }
}

class _ClipRectState extends State<ClipRectView> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: const Text("ClipRect Demo"),
        ),

        /// 不用`ClipRect`包裹的话，Align的height/widthFactor属性不会生效
        body: ClipRect(
            child: Align(
          alignment: Alignment.topCenter,
          heightFactor: 0.5,
          child: Image.asset("images/birds_by_pond.jpg"),
        )));
  }
}

/// 测试`SlideTransition`类
class _SlideTransitionView extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return _SlideTransitionState();
  }
}

class _SlideTransitionState extends State<_SlideTransitionView>
    with SingleTickerProviderStateMixin {
  AnimationController _controller;
  Animation<Offset> _animation;

  @override
  void initState() {
    _controller = AnimationController(
        vsync: this, duration: const Duration(milliseconds: 500));
    Offset begin = Offset(0.0, 0.0);
    Offset end = Offset(1.0, 0.0);
    _animation = Tween(begin: begin, end: end).animate(_controller);
    super.initState();
  }

  @override
  void dispose() {
    _controller?.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: const Text("SlideTransition Demo"),
        ),
        body: Column(
          children: <Widget>[
            RaisedButton(
              onPressed: () {
                handleAnimation(_controller);
              },
              child: Text("开始动画"),
            ),
            SlideTransition(
              position: _animation,
              child: Image.asset("images/birds_by_pond.jpg"),
            )
          ],
        ));
  }
}

/// 测试`SizeTransition`类
class _SizeTransitionView extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return _SizeTransitionState();
  }
}

class _SizeTransitionState extends State<_SizeTransitionView>
    with SingleTickerProviderStateMixin {
  AnimationController _controller;

  @override
  void initState() {
    _controller = AnimationController(
        vsync: this, duration: const Duration(milliseconds: 500));
    super.initState();
  }

  @override
  void dispose() {
    _controller?.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: const Text("Size Transition Demo"),
        ),
        body: SizedBox.expand(
            child: Center(
          child: Column(
            children: <Widget>[
              RaisedButton(
                onPressed: () {
                  handleAnimation(_controller);
                },
                child: const Text("开始动画"),
              ),
              SizeTransition(
                sizeFactor: _controller,
                child: Image.asset("images/birds_by_pond.jpg"),
              ),
              SizedBox(
                height: 10,
              ),
              Image.asset("images/birds_by_pond.jpg")
            ],
          ),
        )));
  }
}

void handleAnimation(AnimationController controller) {
  switch (controller?.status) {
    case AnimationStatus.dismissed:
      controller?.forward();
      break;
    case AnimationStatus.completed:
      controller?.reverse();
      break;
    default:
      break;
  }
}

/// TODO 平移动画->SlideTransition 通过 FractionalTranslation 实现功能  透明度Alpha动画 FadeTransition 该对象是一个 SingleChildRenderObjectWidget 对象
/// SlideTransition 与 PositionedTransition 都会涉及到`Tween`动画的使用，因为他们是 Offset 与 RelativeRect 对象的动画
///  旋转动画->RotationTransition[通过 Transform + Matrix4 配合使用完成的功能] 缩放动画->ScaleTransition[实现原理同 RotationTransition]
/// 尺寸动画-> SizeTransition 底层通过[ClipRect + Align ]配合实现功能，实现控件尺寸的动态改变
